package pers.mihao.ancient_empire.core.manger.strategy.move_area;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import pers.mihao.ancient_empire.base.bo.GameMap;
import pers.mihao.ancient_empire.base.bo.Site;
import pers.mihao.ancient_empire.base.bo.UnitInfo;
import pers.mihao.ancient_empire.base.entity.Ability;
import pers.mihao.ancient_empire.base.entity.RegionMes;
import pers.mihao.ancient_empire.base.entity.UnitLevelMes;
import pers.mihao.ancient_empire.base.entity.UserRecord;
import pers.mihao.ancient_empire.base.enums.AbilityEnum;
import pers.mihao.ancient_empire.base.enums.StateEnum;
import pers.mihao.ancient_empire.common.annotation.KnowledgePoint;
import pers.mihao.ancient_empire.common.config.AppConfig;
import pers.mihao.ancient_empire.core.dto.MovePathDTO;
import pers.mihao.ancient_empire.core.dto.PathPosition;
import pers.mihao.ancient_empire.core.manger.strategy.AbstractStrategy;

/**
 * @author mihao
 */
@Service
public class MoveAreaStrategy extends AbstractStrategy<MoveAreaStrategy> {

    Logger log = LoggerFactory.getLogger(this.getClass());

    private static MoveAreaStrategy instance = null;

    public static MoveAreaStrategy getInstance() {
        if (instance == null) {
            instance = new MoveAreaStrategy();
        }
        return instance;
    }

    /**
     * 虚弱剩余move力
     */
    private static final Integer WEAK_LAST_SPEED = AppConfig.getInt("unitMes.weak.buff.lastSpeed");

    /**
     * 获取移动区域
     *
     * @param userRecord
     * @param unitInfo
     * @return
     */
    public List<Site> getMoveArea(UserRecord userRecord, UnitInfo unitInfo) {
        List<Site> sites = new ArrayList<>();
        int speed = unitInfo.getLevelMes().getSpeed() + 1;
        if (StateEnum.WEAK.type().equals(unitInfo.getStatus())) {
            speed = WEAK_LAST_SPEED + 1;
        }
        int[][] isVisit;
        for (MoveAreaStrategy moveAreaStrategy : getAbilityStrategy(unitInfo.getAbilities())) {
            isVisit = new int[userRecord.getGameMap().getRow() + 1][userRecord.getGameMap().getColumn() + 1];
            if (moveAreaStrategy != null) {
                moveAreaStrategy
                    .getMoveSite(isVisit, unitInfo.getRow(), unitInfo.getColumn(), speed, userRecord, sites);
            }
        }
        if (sites.size() == 0) {
            isVisit = new int[userRecord.getGameMap().getRow() + 1][userRecord.getGameMap().getColumn() + 1];
            getMoveSite(isVisit, unitInfo.getRow(), unitInfo.getColumn(), speed, userRecord, sites);
        }
        return sites.stream().distinct().collect(Collectors.toList());
    }


    /**
     * 获取二次移动区域
     *
     * @param userRecord
     * @param unit
     * @param path
     * @return
     */
    public List<Site> getSecondMoveArea(UserRecord userRecord, UnitInfo unit, List<PathPosition> path) {
        // 2. 判断是否有二次移动
        List<Site> sites = null;
        // 攻击者能力
        UnitLevelMes levelMes;
        for (Ability ability : unit.getAbilities()) {
            if (ability.getType().equals(AbilityEnum.ASSAULT.type())) {
                // 是可以进行二次移动
                levelMes = unit.getLevelMes();
                int lastSpeed =
                    levelMes.getSpeed() - getMoveUseSpeed(userRecord.getGameMap(), path, unit.getAbilities());
                if (lastSpeed > 0) {
                    levelMes.setSpeed(lastSpeed);
                    sites = getMoveArea(userRecord, unit);
                    return sites;
                }
            }
        }
        return sites;
    }


    /**
     * 获取 单位的剩余移动力
     */
    private int getMoveUseSpeed(GameMap map, List<PathPosition> path, List<Ability> abilities) {
        int sum = 0;
        if (path != null) {
            for (int i = 0; i < path.size() - 1; i++) {
                PathPosition p1 = path.get(i);
                PathPosition p2 = path.get(i + 1);
                sum += getPathPositionLength(map, p1, p2, abilities);
            }
        }
        return sum;
    }

    /**
     * @param p1
     * @param p2
     * @return
     */
    private int getPathPositionLength(GameMap map, PathPosition p1, PathPosition p2, List<Ability> abilities) {
        // 如果是同一行
        int sum = 0;
        if (p1.getRow().equals(p2.getRow())) {
            int minC, maxC;
            if (p1.getColumn() < p2.getColumn()) {
                minC = p1.getColumn();
                maxC = p2.getColumn();
            } else {
                minC = p2.getColumn();
                maxC = p1.getColumn();
            }
            int minDeplete, temDep;
            for (int i = maxC; i > minC; i--) {
                minDeplete = Integer.MAX_VALUE;
                for (MoveAreaStrategy strategy : getAbilityStrategy(abilities)) {
                    temDep = strategy.getRegionDeplete(map, p1.getRow(), i);
                    minDeplete = Math.min(minDeplete, temDep);
                }
                sum += minDeplete;
            }
        } else {
            int minR, maxR;
            if (p1.getRow() < p2.getRow()) {
                minR = p1.getRow();
                maxR = p2.getRow();
            } else {
                minR = p2.getRow();
                maxR = p1.getRow();
            }
            int minDeplete, temDep;
            int index;
            for (int i = maxR; i > minR; i--) {
                minDeplete = Integer.MAX_VALUE;
                for (MoveAreaStrategy strategy : getAbilityStrategy(abilities)) {
                    index = (p1.getColumn() - 1) * map.getColumn() + i - 1;
                    temDep = strategy.getRegionDeplete(map, index);
                    minDeplete = Math.min(minDeplete, temDep);
                }
                sum += minDeplete;
            }
        }
        return sum;
    }


    /**
     * @param isVisit   初始为-1 值为表示上次访问的时候剩余的移动力
     * @param lastSpeed
     */
    public void getMoveSite(int[][] isVisit, int row, int column, int lastSpeed, UserRecord userRecord,
        List<Site> sites) {
        GameMap gameMap = userRecord.getGameMap();
        if ((isVisit[row][column] != 0 && isVisit[row][column] >= lastSpeed) || isHaveEnemy(userRecord, row, column)) {
            return;
        }
        // 记录已经访问
        isVisit[row][column] = lastSpeed;
        sites.add(new Site(row, column));
        int deplete;
        if (row - 1 > 0 && lastSpeed - (deplete = getRegionDeplete(gameMap, row - 1, column)) > 0) {
            getMoveSite(isVisit, row - 1, column, lastSpeed - deplete, userRecord, sites);
        }
        if (row + 1 <= gameMap.getRow() && lastSpeed - (deplete = getRegionDeplete(gameMap, row + 1, column)) > 0) {
            getMoveSite(isVisit, row + 1, column, lastSpeed - deplete, userRecord, sites);
        }
        if (column - 1 > 0 && lastSpeed - (deplete = getRegionDeplete(gameMap, row, column - 1)) > 0) {
            getMoveSite(isVisit, row, column - 1, lastSpeed - deplete, userRecord, sites);
        }
        if (column + 1 <= gameMap.getColumn()
            && lastSpeed - (deplete = getRegionDeplete(gameMap, row, column + 1)) > 0) {
            getMoveSite(isVisit, row, column + 1, lastSpeed - deplete, userRecord, sites);
        }
    }

    /**
     * 获取上面的地形的消耗
     * @param gameMap
     * @param row
     * @param column
     * @return
     */
    private int getRegionDeplete(GameMap gameMap, int row, int column) {
        // 获取上面地形的type
        int index = (row - 1) * gameMap.getColumn() + column - 1;
        return getRegionDeplete(gameMap, index);
    }

    @KnowledgePoint("使用迪杰斯特拉算法计算'无向有权图'两点的最短路径")
    public MovePathDTO getUnitMovePath(Site startSite, Site aimSite, UserRecord record, UnitInfo unitInfo) {
        GameMap gameMap = record.getGameMap();
        MovePathDTO movePathDTO;
        int graphSize = gameMap.getRegions().size();
        int startIndex = getRegionIndexBySite(startSite, gameMap), endIndex = getRegionIndexBySite(aimSite, gameMap);
        // 保存路径
        boolean[] isVisit = new boolean[graphSize];

        int[] visitPath = new int[graphSize];
        int[] lastVisitIndex = new int[graphSize];
        // 1.初始化
        for (int i = 0; i < graphSize; i++) {
            visitPath[i] = Integer.MAX_VALUE;
            lastVisitIndex[i] = -1;
        }
        visitPath[startIndex] = 0;
        isVisit[startIndex] = true;

        int lastAddIndex = startIndex, column = gameMap.getColumn();

        while (!isVisit[endIndex]) {
            // 2. update 更新新加入的节点 相连的点 更新最短路径
            if (lastAddIndex >= column) {
                updateIndexValue(visitPath, lastVisitIndex, lastAddIndex, lastAddIndex - column, unitInfo, record,
                    endIndex);
                // 更新上面的节点
            }
            if (lastAddIndex % column > 0) {
                // 更新左边的点
                updateIndexValue(visitPath, lastVisitIndex, lastAddIndex, lastAddIndex - 1, unitInfo, record, endIndex);
            }
            if ((lastAddIndex + 1) % column != 0) {
                // 更新右边的点
                updateIndexValue(visitPath, lastVisitIndex, lastAddIndex, lastAddIndex + 1, unitInfo, record, endIndex);
            }
            if (graphSize - lastAddIndex > column) {
                // 更新下面边的点
                updateIndexValue(visitPath, lastVisitIndex, lastAddIndex, lastAddIndex + column, unitInfo, record,
                    endIndex);
            }

            // 3. scan 从未访问过的节点找到最小的路径
            int minPath = Integer.MAX_VALUE;
            for (int i = 0; i < graphSize; i++) {
                if (!isVisit[i]) {
                    if (visitPath[i] < minPath) {
                        minPath = visitPath[i];
                        lastAddIndex = i;
                    }
                }
            }
            if (minPath == Integer.MAX_VALUE) {
                // 不可达的
                movePathDTO = new MovePathDTO();
                movePathDTO.setDeplete(Integer.MAX_VALUE);
                return movePathDTO;
            }
            // 4. 将找到的点作为新的点加入
            isVisit[lastAddIndex] = true;
        }
        movePathDTO = new MovePathDTO();
        movePathDTO.setDeplete(visitPath[endIndex]);

        int tempIndex = endIndex;
        List<Integer> visitIndex = new ArrayList<>();
        visitIndex.add(tempIndex);
        while (tempIndex != startIndex) {
            // visit
            visitIndex.add(lastVisitIndex[tempIndex]);
            tempIndex = lastVisitIndex[tempIndex];
        }
        List<PathPosition> pathPositions = new ArrayList(visitIndex.size());
        for (int i = visitIndex.size() - 1; i >= 0; i--) {
            pathPositions.add(new PathPosition(getSiteByRegionIndex(visitIndex.get(i), gameMap)));
        }

        // 返回需要转折点的关键点
        List<PathPosition> turningPoint = new ArrayList<>();
        turningPoint.add(new PathPosition(startSite));
        for (int i = visitIndex.size() - 2; i >= 0; i--) {
            Site p = getSiteByRegionIndex(visitIndex.get(i), gameMap);
            if (p.getRow().equals(startSite.getRow()) || p.getColumn().equals(startSite.getColumn())) {
                continue;
            }
            startSite = getSiteByRegionIndex(visitIndex.get(i + 1), gameMap);
            turningPoint.add(new PathPosition(startSite));
        }
        turningPoint.add(new PathPosition(getSiteByRegionIndex(visitIndex.get(0), gameMap)));
        // 将路径长度放进去
        for (int i = 0; i < turningPoint.size() - 1; i++) {
            PathPosition p = turningPoint.get(i);
            p.setLength(getPathPositionLength(p, turningPoint.get(i + 1)));
        }

        movePathDTO.setPositionList(turningPoint);
        return movePathDTO;
    }

    private void updateIndexValue(int[] visitPath, int[] lastVisitIndex, int lastAddIndex, int compareIndex,
        UnitInfo unitInfo, UserRecord record, int endIndex) {
        int deplete;
        if (endIndex == compareIndex) {
            // 如果该点就是目标点 就是0
            deplete = 0;
        } else {
            deplete = getDepleteByIndex(unitInfo, record, compareIndex);
        }
        // 更新上面的节点
        if (deplete != Integer.MAX_VALUE && visitPath[lastAddIndex] + deplete < visitPath[compareIndex]) {
            visitPath[compareIndex] = visitPath[lastAddIndex] + deplete;
            lastVisitIndex[compareIndex] = lastAddIndex;
        }
    }

    private int getDepleteByIndex(UnitInfo unitInfo, UserRecord record, int index) {
        return getRegionDepleteByUnitInfo(unitInfo, record, index);
    }

    /**
     * 获取单位在地图上的某一点需要消耗的移动力
     *
     * @param unitInfo
     * @param record
     * @param index
     * @return
     */
    private int getRegionDepleteByUnitInfo(UnitInfo unitInfo, UserRecord record, int index) {
        int minDeplete, temDep;
        if (isHaveEnemy(record, getSiteByRegionIndex(index, record.getGameMap()))) {
            return Integer.MAX_VALUE;
        }
        minDeplete = getRegionDeplete(record.getGameMap(), index);
        for (MoveAreaStrategy strategy : getAbilityStrategy(unitInfo.getAbilities())) {
            temDep = strategy.getRegionDeplete(record.getGameMap(), index);
            minDeplete = Math.min(minDeplete, temDep);
        }
        return minDeplete;
    }

    private int getPathPositionLength(PathPosition p1, PathPosition p2) {
        return Math.abs(p1.getRow() - p2.getRow()) + Math.abs(p1.getColumn() - p2.getColumn());
    }

    /**
     * 子类需要重写的对地形的
     * @param gameMap
     * @param index
     * @return
     */
    protected int getRegionDeplete(GameMap gameMap, int index) {
        // 获取上面地形的type
        String type = gameMap.getRegions().get(index).getType();
        RegionMes regionMes = regionMesService.getRegionByTypeFromLocalCatch(type);
        return regionMes.getDeplete();
    }
}
